#include "commands.h"

static symtabADT commandTable;
dbT db;

static void defineCommand(string cmd, commandFnT fn);
static lineT fetchNextStatement(void);
static void LIST(lineT line);
static void HELP(lineT line);
static void RUN(lineT line);
static void QUIT(lineT line);
static void LET(lineT line);
static void PRINT(lineT line);
static void INPUT(lineT line);
static void REM(lineT line);
static void GOTO(lineT line);
static void IF(lineT line);
static void END(lineT line);

void initCommandTable() {
	commandTable = NewSymbolTable();
	defineCommand("RUN", RUN, commandTable);
	defineCommand("LIST", LIST, commandTable);
	defineCommand("HELP", HELP, commandTable);
	defineCommand("QUIT", QUIT, commandTable);
	defineCommand("LET", LET, commandTable);
	defineCommand("PRINT", PRINT, commandTable);
	defineCommand("INPUT", INPUT, commandTable);
	defineCommand("REM", REM, commandTable);
	defineCommand("GOTO", GOTO, commandTable);
	defineCommand("IF", IF, commandTable);
	defineCommand("END", END, commandTable);
	
	db->cmdTable = commandTable;
}

static void defineCommand(string cmd, commandFnT fn, symtabADT cmdTable) {
	commandEntryT entry;
	
	entry = New(commandEntryT);
	entry->fn = fn;
	Enter(cmdTable, cmd, entry);
}

void executeCommand(lineT line) {
	commandEntryT entry;

	entry = (commandEntryT) Lookup(commandTable, line.cmd);
	if (entry == UNDEFINED) {
		printf("Undefined command: %s\n", line.cmd);
		return;
	}
	entry->fn(line);
}

bool runnableLine(lineT line) {
	bool rowNum = (line.row > -1 ? TRUE : FALSE);

	// Commands that must have line numbers:
	if (StringEqual(line.cmd, "REM") || StringEqual(line.cmd, "GOTO") || StringEqual(line.cmd, "IF") || StringEqual(line.cmd, "END"))
		return FALSE;
	// Commands optional line number:
	if (rowNum && (StringEqual(line.cmd, "LET") || StringEqual(line.cmd, "PRINT") || StringEqual(line.cmd, "INPUT") || StringEqual(line.cmd, "")))
		return FALSE;

	// Update the program counter:
	//db->PC = rowToIndex(line.row);
	
	// Run the command:
	return TRUE;
}

/* fethNextStatement
 * -----------------------
 * Fetches the next instruction and returns it as a lineT struct
 * db->PC stores the index for the line that's going to be executed next
 */
static lineT fetchNextStatement() {
	int i, row, minIndex = INT_MAX, minRow = INT_MAX;
	lineT nextInstruction;

	// Next instruction:
	nextInstruction = db->lines.entries[db->PC];

	// Look for the next instruction:
	for (i=0; i<db->lines.numRows; i++) {
		row = db->lines.entries[i].row;

		// Find the minimum row number, that's greater than the upcoming instructions
		if (row < minRow && row > nextInstruction.row) {
			minRow = db->lines.entries[i].row;
			minIndex = i;
		}
	}
	db->PC = minIndex;

	return nextInstruction;
}

/* Run program */ 
static void RUN() { 
	lineT instruction; 
	int i, min = INT_MAX;

	for (i=0; i<db->lines.numRows; i++) {
		if (db->lines.entries[i].row < min) {
			min = db->lines.entries[i].row;
			db->PC = i;
		}
	}

	if (db->lines.numRows > 0) { 
		do { 
			instruction = fetchNextStatement();		// Next instruction 
			executeCommand(instruction);			// Execute command 
		} while (db->PC < db->lines.numRows); 
	} 
}

/* Quit program */
static void QUIT(lineT line) {
	exit(0);
}

/* List the program code */
static void LIST(lineT line) {
	int i, j, min, index, pc = 0;
	
	for (i=0; i<db->lines.numRows; i++) {
		min = INT_MAX;
		for (j=0; j<db->lines.numRows; j++) {
			if (db->lines.entries[j].row < min && db->lines.entries[j].row > pc) {
				index = j;
				min = db->lines.entries[j].row;
			}
		}
		printf("%d %s%s\n", db->lines.entries[index].row, db->lines.entries[index].cmd, db->lines.entries[index].rest);
		pc = min;
	}
}

/* Prints a line */
static void PRINT(lineT line) {
	string token;
	int value;
	expressionADT exp;

	SetScannerSpaceOption(db->scanner, IgnoreSpaces);
	SetScannerString(db->scanner, line.rest);
	
	//try {
		exp = ParseExp(db->scanner);
		value = EvalExp(exp);
		printf("%d\n", value);
	//	except(ErrorException)
	//		printf("Error: %s\n", (string) GetExceptionValue());
	//} endtry;
}

/* Input - save input as a variable in the variable table */
static void INPUT(lineT line) {
	string variable="";
	int value;

	SetScannerString(db->scanner, line.rest);
	
	// Variable name:
	variable = ReadToken(db->scanner);
	
	// Variable value:
	printf(" ? ");
	value = GetInteger();
	// Save variable to the table:
	//try {
		SetIdentifierValue(variable, value);
	//	except(ErrorException)
	//		printf("Error: %s\n", (string) GetExceptionValue());
	//} endtry;
}

/* LET: assign variable */
static void LET(lineT line) {
	int value=-1;
	string variable, token, expression="";
	expressionADT exp;

	SetScannerString(db->scanner, line.rest);
	
	// 1. Variable name:
	variable = ReadToken(db->scanner);

	// 2. Remove the '=' sign:
	ReadToken(db->scanner);

	// 3. Parse & evaluate the expression:
	//try {
		exp = ParseExp(db->scanner);
		value = EvalExp(exp);
	//	except(ErrorException)
	//		printf("Error: %s\n", (string) GetExceptionValue());
	//} endtry;

	// 4. Save variable to the table:
	SetIdentifierValue(variable, value);
}

/* Support for comments... Ha! */
static void REM(lineT line) {
	return;
}

/* Goto line */
static void GOTO(lineT line) {
	int rowNum;
	
	SetScannerString(db->scanner, line.rest);
	
	//try {
		rowNum = StringToInteger(ReadToken(db->scanner));
		db->PC = rowToIndex(rowNum);
	//} endtry;
}

/* If statement... */
static void IF(lineT line) {
	lineT instruction;
	expressionADT exp1, exp2;
	int val1, val2, gotoRow;
	string token;
	char op;

	SetScannerString(db->scanner, line.rest);
	
	// 1. 1st expression:
	exp1 = ReadE(db->scanner);
	val1 = EvalExp(exp1);

	// 2. Operator:
	token = ReadToken(db->scanner);
	op = token[0];

	// 3. 2nd expression:
	exp2 = ReadE(db->scanner);
	val2 = EvalExp(exp2);

	// 4. Get row number:
	token = ReadToken(db->scanner);
	if (StringEqual(token, "THEN")) {
		token = ReadToken(db->scanner);
		gotoRow = rowToIndex(StringToInteger(token));
	}

	// Operator switch:
	// Update the Program Counter if expression is true
	switch (op) {
		case '=':
			if (val1 == val2)
				db->PC = gotoRow;
			break;
		case '>':
			if (val1 > val2)
				db->PC = gotoRow;
			break;
		case '<':
			if (val1 < val2)
				db->PC = gotoRow;
			break;
	}
}

/* Ends the program execution */
static void END(lineT line) {
	db->PC = INT_MAX;
}

/* Prints the help section */
static void HELP(lineT line) {
	printf("This program implements a minimal basic interpreter.\n\n");
	printf("Commands:\n");
	printf(" RUN            -- Run current program\n");
	printf(" LIST           -- Lists the program\n");
	printf(" HELP           -- This message\n");
	printf(" QUIT           -- Exits from interpreter\n\n");
	printf("Statements:\n");
	printf(" REM...         -- Remark\n");
	printf(" LET v = exp    -- Assignment\n");
	printf(" INPUT v        -- Reads value from user\n");
	printf(" PRINT exp      -- Prints value of expression\n");
	printf(" GOTO n         -- Goes to line n\n");
	printf(" If c THEN n    -- Goes to line n if c is TRUE\n\n");
}